#include "AbstractObject.h"
#include "Sheet.h"
#include "Logger.h"
#include "ObjectFactory.h"
#include "LuaUtils.h"

RegisterCppObj<AbstractObject> g_register_abstract_object;

AOPtrSled::AOPtrSled()
{
	_target = NULL;
	_possessiveRefs = 0;
	_weakRefs = 0;
}

AOPtrSled::AOPtrSled(AbstractObject* target)
{
	_target = target;
	_possessiveRefs = 0;
	_weakRefs = 0;
}

// Nonvirtual stuff
AbstractObject::AbstractObject()
{
	_sled = new AOPtrSled(this);
	_luaref = -1;
	_sh = NULL;
	_luaname = "AbstractObject";
	_name = "Unnamed";
	_noUpdateWarningSuppression = false;
}

AOPtrSled* AbstractObject::getPtrSled() const
{
	return _sled;
}

// set data to NULL to delete key
void AbstractObject::setMetaData(std::string name, void *data)
{
	if (data)
	{
		// Update object in table (clobbering any existing data) 
		_metaData[name] = data;		
	}
	else
	{
		_metaData.erase(name);
	}	
}

void *AbstractObject::getMetaData(std::string name)
{
	if (_metaData.count(name))
	{
		return _metaData[name];
	}
	else
	{
		return NULL;
	}
}

void AbstractObject::setLuaRef(int lr)
{
	_luaref = lr;
}

int AbstractObject::getLuaRef()
{
	return _luaref;
}

void AbstractObject::setSheet(Sheet* sh)
{
	_sh = sh;
}

Sheet* AbstractObject::getSheet()
{
	return _sh;
}

// stuff

// Return the c++ type id
int AbstractObject::getCppTypeId()
{
	return 1;
}

// Returns whether this can be cast to a given type
bool AbstractObject::isType(int id)
{
	return id == 1;
}

// Return the c++ type as a string 
std::string AbstractObject::getCppTypeName()
{
	return "AbstractObject";
}

// Return the lua type as a string
std::string AbstractObject::getLuaTypeName()
{
	return _luaname;
}

void AbstractObject::setLuaTypeName(std::string newname)
{
	_luaname = newname;
}

// returns the 'name' of this thing (however it wants to call itself)
std::string AbstractObject::getName()
{
	return _name;
}

void AbstractObject::setName(std::string newname)
{
	_name = newname;
}

// returns a description of this thing
std::string AbstractObject::getDescription()
{
	std::string ret = getName() + "(" + getLuaTypeName() + ")";
	return ret;
}

// create another one of these
AbstractObject* AbstractObject::newInstance()
{
	return new AbstractObject;
}

// init & update
void AbstractObject::init()
{
	// Call init on our lua ref
	if(_luaref >= 0)
	{
		lua_State* L = _sh->getL();
		lua_rawgeti(L, LUA_REGISTRYINDEX, _luaref);
		lua_getfield(L, -1, "init");
		if(!lua_isfunction(L, -1))
		{
			LOG(logERROR) << "AO based lua object has no init function.";
			lua_settop(L, -3);
			return;
		}
		lua_insert(L, -2);
		if(lua_pcall(L, 1, 0, 0) != 0) // error!
		{
			LOG(logERROR) << "Lua init call failed for " << getDescription() << ": " << std::string(getSafeLuaString(L, -1));
			lua_settop(L, -2);
		}
	}
}

void AbstractObject::update(double dt)
{
	// call update on our lua ref
	if(_luaref >= 0)
	{
		lua_State* L = _sh->getL();
		lua_rawgeti(L, LUA_REGISTRYINDEX, _luaref);
		lua_getfield(L, -1, "update");
		if(!lua_isfunction(L, -1))
		{
			if(!_noUpdateWarningSuppression)
			{
				LOG(logERROR) << "AO based lua object has no update. (suppressing further warnings)";
				_noUpdateWarningSuppression = true;
			}
			lua_settop(L, -3);
			return;
		}
		lua_insert(L, -2);
		lua_pushnumber(L, dt);
		if(lua_pcall(L, 2, 0, 0) != 0) // error!
		{
			LOG(logERROR) << "Lua update call failed for " << getDescription() << ": " << std::string(getSafeLuaString(L, -1));
			lua_settop(L, -2);
		}
	}
}

// interact
IData AbstractObject::interact(std::string interactionName, IData& interData)
{
	// call interact on our lua ref
	if(_luaref >= 0)
	{
		lua_State* L = _sh->getL();
		lua_rawgeti(L, LUA_REGISTRYINDEX, _luaref);
		lua_getfield(L, -1, interactionName.c_str());
		if(!lua_isfunction(L, -1))
		{
			return IData(NULL);
		}
		lua_insert(L, -2);
		pushIData(L, interData);

		if(lua_pcall(L, 2, 1, 0) != 0) // error!
		{
			LOG(logERROR) << "Lua interact call [" << interactionName << "] failed for " << getDescription() << ": " << std::string(getSafeLuaString(L, -1));
			lua_settop(L, -2);
			return IData(NULL);
		}
		else
		{
			//IData ret = popIData(L, 1);
			//return ret;
			return IData(NULL);
		}
	}

	// We no longer have to do anything explicit here
	//InterData::release(interData);
	return IData(NULL);
}

// push its static functions into the given lua table
void AbstractObject::pushStaticFuncs(lua_State* L)
{
	// nothing to put here yet
}

void AbstractObject::pushSelf(lua_State* L)
{
	// just get our reference if it exists
	if(_luaref > 0)
		lua_rawgeti(L, LUA_REGISTRYINDEX, _luaref);
	else
	{
		LOG(logERROR) << "Trying to push " << getCppTypeName() << " with no associated lua table!";
		lua_pushnil(L);
	}
}

AbstractObject::~AbstractObject()
{
	lua_State* L = _sh->getL();
	if(_luaref > 0)
		luaL_unref(L, LUA_REGISTRYINDEX, _luaref);
	if(_sled->_possessiveRefs + _sled->_weakRefs <= 0)
	{
		LOG(logDEBUG) << "Deleting sled....";
		delete _sled;
	}
	else
	{
		LOG(logDEBUG) << "Nulling sled....";
		_sled->_target = NULL;
	}
}
